package org.jeecg.modules.cascv.image.service.video;

import cn.hutool.core.util.HexUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.util.StringUtil;
import org.bytedeco.javacpp.*;
import org.jeecg.common.lineparser.Line;
import org.jeecg.common.lineparser.LineParser;
import org.jeecg.modules.cascv.image.util.LaptopConstant;
import org.jeecg.modules.cascv.image.util.LaptopUtil;
import org.jeecg.modules.cascv.image.util.MergeUtil;
import org.jeecg.modules.message.websocket.WebSocket;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;

@Service
@Slf4j
public class VideoService {


    private static ReadCallback readCallback = new ReadCallback();
    @Value("${srsAddress}")
    private String srsAddress;
    @Resource
    private WebSocket webSocket;


    @Async(value = "threadPoolTaskExecutor")
    public void play(String userId, String idsName, String key, int version, Long ts) {

        Long startOffset = 0L;
        if (ts != null && ts > 0) { //判断有没有拖拽
            //索引 文件结构，Innteger:秒值 Long : offset
            Cache<Integer, Long> indexCache = LaptopConstant.fileIndexCache.get(key); //从文件索引里面去找
            if (indexCache != null) {
                Long second = ts / 1000000; //前端传的数据 取出秒值来
                startOffset = indexCache.getIfPresent(second.intValue()); //根据秒值再取出startOffset

                if (startOffset == null) {
                    for (int i = 0; i < 10; i++) {
                        second -= 10;//如果startOffset为null 就往前推10秒，主要是担心异常数据
                        startOffset = indexCache.getIfPresent(second.intValue());
                        if (startOffset != null) {
                            break;
                        }
                    }
                }
            }
            log.info("startOffset:" + startOffset);
            if (startOffset == null) { //如果真的没有就从头开始读
                startOffset = 0L;
            }
        }
//进度放到共享内存里面 ，因为拉流的过程是调用的C++方法，然后read的线程时候就可以拿到
        LaptopConstant.offsetMap.put(key + "#" + userId + "#" + version, startOffset);
        Integer currVersion = null;

        //初始化的
        String filePath = LaptopConstant.filePathMap.getOrDefault(key, "");
        Path path = Paths.get(filePath);
        Charset cs = StandardCharsets.UTF_8;
        LineParser parser = new LineParser(Integer.MAX_VALUE, startOffset);

        final String[] lastVideoStr = {null};


        final Long[] lastOffset = {0L};
        AtomicInteger maxPushNum = new AtomicInteger(0);
        //取关键帧
        try {
            parser.forEach(path, cs, new Consumer<Line>() { // 可以快速读取文件
                @Override
                public void accept(Line line) {
                    if (line.getLength() <= 100) {
                        return;
                    }

                    JSONObject dataItem = null;
                    try {
                        dataItem = JSON.parseObject(line.getContent().toString());
                    } catch (Exception e) {
                        return;
                    }
                   String tableName =dataItem.getString("tableName");
                    String secondStr = dataItem.getString("second");
                    String subsecondStr = dataItem.getString("subsecond");
                    dataItem = dataItem.getJSONObject("Content");
                    //A1_VGR_MICU_APP14DATA_20_2021_1

                    System.out.println("key = " + key);
                    String videoStr = dataItem.getString(LaptopConstant.FRAME_FIELD_MAP.get(tableName));//todo
                   // videoStr = StringUtils.substring(videoStr, 7, videoStr.length() - 10);
                    //videoStr = StringUtils.substring(videoStr, 7, videoStr.length() - 16);
                    videoStr = (videoStr.split(";")[1]).substring(2).trim();
                    if (maxPushNum.get() == 0 || maxPushNum.get() % 10 == 0) {
                       // String secondStr = dataItem.getString(LaptopConstant.SECOND_FIELD_MAP.get(tableName));//todo
                        long second = Long.parseLong(secondStr);
                        secondStr = LaptopUtil.toDayHoursMinSec(second);
                        //secondStr = secondStr.split(";")[1];
                       // Long second = LaptopUtil.getSecondTime(secondStr);
                        //String subSecondStr = dataItem.getString(LaptopConstant.SUB_SECOND_FIELD_MAP.get(tableName));//todo
                       // Integer subSecond = Integer.parseInt(subSecondStr.split(";")[1]);
                        int subSecond = Integer.parseInt(subsecondStr);
                        System.out.println("subSecond = " + subSecond);
                        Long dataTs = second * 1000000 + subSecond;
                        String[] split = secondStr.split(":");
                        dataItem.put("ts", split[0] + "天" + split[1] + "时" + split[2] + "分" + split[3] + "秒" + subsecondStr );
                        dataItem.put("shiptime", dataTs);
                        dataItem.put("shiptime-second", second + ";" + second);
                        dataItem.put("shiptime-all", dataTs + ";" + dataTs);
                        webSocket.pushMessage(userId, "DATA:" + dataItem.toJSONString());
                    }

                    if (lastVideoStr[0] == null) {

                    } else {
                        String ss = lastVideoStr[0] + videoStr;
                        int headerIndex = ss.indexOf("000001"); //每一帧的帧头为0000001
                        if (headerIndex < 0) {
                            return;
                        }
                        String typeStr = ss.substring(headerIndex + 6, headerIndex + 8);//找一个字节转为16进制
                        int type = Integer.parseUnsignedInt(typeStr, 16);
                        type = type & 0x1f; //取后四位 取出值 判断它的类型是多少
                        System.out.println("typeStr=" + typeStr + ",type=" + type);

                        if (type == 7 || type == 8 || type == 5) { //如果是 5，7，8就是一个关键帧
                            System.out.println(version + "找到I帧" + lastOffset[0]);
                            LaptopConstant.offsetMap.put(key + "#" + userId + "#" + version, lastOffset[0]);
                            throw new RuntimeException("找到I帧" + lastOffset[0]);
                        }
                    }
                    maxPushNum.incrementAndGet(); //++1
                    lastVideoStr[0] = videoStr;
                    lastOffset[0] = line.getOffset();
                }
            });
        } catch (Exception e) {
        }


        //开始建立连接
        avformat.AVFormatContext ifmt_ctx = new avformat.AVFormatContext(null); //读取指针 作为一个输入端
        avformat.AVFormatContext ofmt_ctx = new avformat.AVFormatContext(null);
//        String out_filename = "rtmp://172.15.10.188:1935/live/livestream/" + conId + "/" + UUID + "/" + version;//
        //向这个地址推流的4  流媒体会建立一个实时播放流
        String out_filename = "rtmp://" + srsAddress + ":1935/live/livestream/" + key + "/" + userId + "/" + version;// 正式 流媒体服务作为一个输出端

        ifmt_ctx = avformat_alloc_context();
        long tempSize = 40960;
        //
        avformat.AVIOContext avio = avformat.avio_alloc_context(new BytePointer(av_malloc(tempSize)), (int) tempSize, 0, ifmt_ctx, readCallback, null, null);
        ifmt_ctx.pb(avio);

        int isReal = StringUtils.isEmpty(idsName) ? 0 : 1;

        String cacheKey = key + "#" + version + "#" + isReal + "#" + userId;
        System.out.println("cacheKey ===> " + cacheKey);
        LaptopConstant.pointerAndConVer.put(ifmt_ctx, cacheKey);

        if (avformat.avformat_open_input(ifmt_ctx, "", null, null) != 0) {
            return;
        }

        if (avformat.avformat_find_stream_info(ifmt_ctx, (PointerPointer<Pointer>) null) < 0) {
            return;
        }

        int videoindex = -1;
        for (int i = 0; i < ifmt_ctx.nb_streams(); i++) {
            if (ifmt_ctx.streams(i).codec().codec_type() == AVMEDIA_TYPE_VIDEO) {
                videoindex = i;
                break;
            }
        }

        if (videoindex == -1) {
            return;
        }

        int flv = avformat_alloc_output_context2(ofmt_ctx, null, "flv", out_filename);

        avformat.AVOutputFormat ofmt = ofmt_ctx.oformat();
        for (int j = 0; j < ifmt_ctx.nb_streams(); j++) {
            avformat.AVStream in_stream = ifmt_ctx.streams(j);
            avformat.AVStream out_stream = avformat_new_stream(ofmt_ctx, in_stream.codec().codec());

            avcodec.AVCodec in_codec = avcodec_find_decoder(in_stream.codecpar().codec_id());
            avcodec.AVCodecContext codec_ctx = avcodec_alloc_context3(in_codec);
            int ret = avcodec_parameters_to_context(codec_ctx, in_stream.codecpar());
            codec_ctx.codec_tag(0);

            if ((ofmt_ctx.oformat().flags() & AVFMT_GLOBALHEADER) > 0) {
                int flags = codec_ctx.flags();
                codec_ctx.flags(flags | AV_CODEC_FLAG_GLOBAL_HEADER);
            }

            ret = avcodec_parameters_from_context(out_stream.codecpar(), codec_ctx);

        }

        av_dump_format(ofmt_ctx, 0, out_filename, 1);


        if ((ofmt.flags() & AVFMT_NOFILE) <= 0) {
//            ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
            AVIOContext pb = new AVIOContext();
            int ret2 = avio_open(pb, out_filename, AVIO_FLAG_WRITE);
            if (ret2 < 0) {
//                System.out.println("avio_open error");
            }
            ofmt_ctx.pb(pb);
        }

        int ret2 = avformat_write_header(ofmt_ctx, (PointerPointer) null);
        if (ret2 < 0) {
//            System.out.println("avformat_write_header error" + ret2);
        }

        AVPacket pkt = null;
        pkt = new AVPacket();
        long start_time = av_gettime();
        int frame_index = 0;
        AVRational baseRat = null;
        Double currSpeed = 1d;
        Double newSpeed = null;
        long lastpts = -1;
        long lastdts = -1;
        long ptsstep = -1;
        long dtsstep = -1;
        long lastsleep = -1;
        boolean speedUp = true;
        boolean changeSpeed = false;


        try {
            currVersion = version;
            while (currVersion - version == 0) {  //版本号判断，历史版本就直接终断掉

                AtomicInteger atomicInteger = LaptopConstant.clientVersionMap.get(userId);
                if (atomicInteger == null) {
                    currVersion = -1;
                } else {
                    currVersion = atomicInteger.get();
                }

                synchronized (Thread.currentThread()) {
                    boolean breakFlag = false;
                    Integer startFlag = LaptopConstant.startOrStop.get(key);
                    if (startFlag == null || startFlag - 0 == 0) {
                        while (currVersion - version == 0 && (startFlag == null || startFlag - 0 == 0)) {
                            try {
                                System.out.println("视频播放暂停---------------------");
                                Thread.currentThread().wait(500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                            atomicInteger = LaptopConstant.clientVersionMap.get(userId);
                            if (atomicInteger == null) {
                                currVersion = -1;
                            } else {
                                currVersion = atomicInteger.get();
                            }
                            startFlag = LaptopConstant.startOrStop.get(key);

                            if (currVersion < 0 || currVersion - version != 0) {
                                breakFlag = true;
                                break;
                            }

                        }

                        if (breakFlag) {
                            break;
                        }
                    }
                }

                newSpeed = currSpeed;

                int ret = av_read_frame(ifmt_ctx, pkt); //一个frame 可能含多个帧， 就是视频帧
                if (ret < 0) {
                    break;
                }
                if (pkt.pts() == AV_NOPTS_VALUE()) {

                    if (currSpeed - newSpeed != 0) {
                        System.out.println("变速");
                        changeSpeed = true;
                        speedUp = newSpeed > currSpeed;
                    } else {
                        changeSpeed = false;
                    }
                    currSpeed = newSpeed;

                    AVRational time_base1 = ifmt_ctx.streams(videoindex).time_base();
                    double calc_duration = AV_TIME_BASE / currSpeed / av_q2d(ifmt_ctx.streams(videoindex).r_frame_rate());
                    pkt.pts((long) (frame_index * calc_duration / (av_q2d(time_base1) * AV_TIME_BASE)));
                    pkt.dts(pkt.pts());
                    pkt.duration((long) (calc_duration / (av_q2d(time_base1) * AV_TIME_BASE / currSpeed)));

                    if (pkt.pts() < lastpts) {
                        if (ptsstep < 0) {
                            ptsstep = lastpts + 1000 - pkt.pts();
                        }
                        pkt.pts(pkt.pts() + ptsstep);
                    }
                    if (pkt.dts() < lastdts) {
                        if (dtsstep < 0) {
                            dtsstep = lastdts + 1000 - pkt.dts();
                        }
                        pkt.dts(pkt.dts() + dtsstep);
                    }


                    lastpts = pkt.pts(); //时间戳 播放时间，读的载核的流是没有时间戳的 所以要赋一个 paly
                    lastdts = pkt.dts(); // data
                }

                if (pkt.stream_index() - videoindex == 0) {
                    AVRational time_base1 = ifmt_ctx.streams(videoindex).time_base();
                    AVRational time_base_q = new AVRational();
                    time_base_q.num(1);
                    time_base_q.den(AV_TIME_BASE);

                    long pts_time = av_rescale_q(pkt.dts(), time_base1, time_base_q);
                    long now_time = av_gettime() - start_time;
//                    System.out.println(speedUp + ":" + pts_time + "," + now_time  + "sleep =" + (pts_time - now_time));
                    if (changeSpeed && speedUp && pts_time < now_time) {
                        int sleepts = (int) (pts_time - now_time);
                        start_time = start_time - sleepts;
                        sleepts = Double.valueOf(lastsleep / currSpeed).intValue();
                        av_usleep(sleepts);
                        lastsleep = sleepts;
                    } else if (pts_time > now_time) {
                        int sleepts = (int) (pts_time - now_time);
                        if (!speedUp) {
                            if (sleepts * currSpeed / lastsleep > 2) {
                                start_time = start_time - sleepts;
                                sleepts = Double.valueOf(lastsleep / currSpeed).intValue();
                            }
                        } else {
                            if (sleepts < 0) {
                                start_time = start_time - sleepts;
                                sleepts = Double.valueOf(lastsleep / currSpeed).intValue();
                            }
                        }
                        av_usleep(sleepts);
                        lastsleep = sleepts;
                    } else {//修改暂停
//                        int sleepts = Double.valueOf(lastsleep / currSpeed).intValue();
//                        if(sleepts > 0){
//                            av_usleep(sleepts);
//                            lastsleep = sleepts;
//                        }
                    }

                }

                AVStream in_stream = ifmt_ctx.streams(pkt.stream_index());
                AVStream out_stream = ofmt_ctx.streams(pkt.stream_index());

                if (baseRat == null) {
                    AVRational avRational = out_stream.time_base();
                    baseRat = new AVRational();
                    baseRat.num(1);
                    baseRat.den(Double.valueOf(avRational.den() / currSpeed).intValue());
                }
                pkt.pts(av_rescale_q_rnd(pkt.pts(), in_stream.time_base(), out_stream.time_base(), (int) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));
                pkt.dts(av_rescale_q_rnd(pkt.dts(), in_stream.time_base(), out_stream.time_base(), (int) (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)));
                pkt.duration(av_rescale_q(pkt.duration(), in_stream.time_base(), out_stream.time_base()));
                pkt.pos(-1);

                if (pkt.stream_index() - videoindex == 0) {
                    frame_index++;
                }
                av_interleaved_write_frame(ofmt_ctx, pkt); //写流 推视频流

                JSONObject dataItem = LaptopConstant.dataTableCache.getIfPresent(key + "#" + userId + "#" + currVersion);

                if (dataItem != null) { //视频播放 右侧数据区播放
                    try {

                        String secondStr = dataItem.getString("second");//todo
                        System.out.println("---videoService :=====: " + secondStr+"---");
                        // secondStr = secondStr.split(";")[1];
                        //Long second = LaptopUtil.getSecondTime(secondStr);
                        long second = Long.parseLong(secondStr);
                        secondStr =  LaptopUtil.toDayHoursMinSec(second);
                        String subSecondStr = dataItem.getString("subsecond");//todo
                        Integer subSecond = Integer.parseInt(subSecondStr);
                        System.out.println("subSecond = " + subSecond);
                        Long dataTs = second * 1000000 + subSecond;
                        String[] split = secondStr.split(":");
                        dataItem = dataItem.getJSONObject("Content");
                        dataItem.put("ts", split[0] + "天" + split[1] + "时" + split[2] + "分" + split[3] + "秒" + subSecondStr);
                        dataItem.put("shiptime", dataTs);
                        dataItem.put("shiptime-second", second + ";" + second);
                        dataItem.put("shiptime-all", dataTs + ";" + dataTs);

                        webSocket.pushMessage(userId, "DATA:" + dataItem.toJSONString());

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            }


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (StringUtils.isNotEmpty(idsName)) {
                System.out.println("实时推流停止：" + idsName + "#" + userId);
            }

            System.out.println("线程结束，version=" + currVersion + "#" + userId);
        }

        av_write_trailer(ofmt_ctx);

        av_free_packet(pkt);
        avformat_close_input(ifmt_ctx);// Close the video file
        avformat_free_context(ofmt_ctx);
    }
}
